#include <stdexcept>
#include "sdl.hpp"

using namespace std;
using namespace sdl::mask;

namespace sdl
{
// =============================================================================
// Rect

Rect::Rect(int X, int Y, int W, int H)
{
    x = X;
    y = Y;
    w = W;
    h = H;
}
Rect::Rect(int X, int Y)
{
    x = X;
    y = Y;
    w = 0;
    h = 0;
}
Rect::Rect()
{
    x = 0;
    y = 0;
    w = 0;
    h = 0;
}

bool Rect::isEnclosing (Rect box)
{
    // w and h are unsigned so they can never be negative
    if (x <  box.x && x + w > box.x + box.w)
        if (y <  box.y && y + h > box.y + box.h)
            return true;
    return false;
}

bool Rect::isOverlapping (Rect box)
{
    // w and h are unsigned so they can never be negative
    if (x + w < box.x)
        return false;
    if (x > box.x + box.w && x + w > box.x + box.w)
        return false;
    if (y + h < box.y)
        return false;
    if (y > box.y + box.h && y + h > box.y + box.h)
        return false;
    return true;
}

void Rect::trim(int amount)
{
    if (w > amount * 2)
    {
        x += amount;
        w -= amount * 2;
    }
    else
    {
        x = x + (w / 2);
        w = 0;
    }

    if (h > amount * 2)
    {
        y += amount;
        h -= amount * 2;
    }
    else
    {
        y = y + (h / 2);
        h = 0;
    }
}


bool operator== (Rect &r1, Rect &r2)
{
    if (r1.x == r2.x && r1.y == r2.y && r1.w == r2.w && r1.h == r1.h)
        return true;
    return false;
}

bool operator!= (Rect &r1, Rect &r2)
{
    return !(r1 == r2);
}

// =============================================================================
// Time

Limiter::Limiter()
{
    this->lasttick = SDL_GetTicks();
}

float Limiter::tick(uint32_t caprate)
{
    if (caprate != 0)
    {
        if ((SDL_GetTicks() - lasttick) < (1000 / caprate))
        {
            SDL_Delay((1000 / caprate) - (SDL_GetTicks() - lasttick));
        }
    }

    uint32_t diff = SDL_GetTicks() - lasttick;
    lasttick = SDL_GetTicks();
    return 1000 / float(diff);
}

uint32_t Limiter::interval()
{
    return SDL_GetTicks() - lasttick;
}

void Limiter::reset()
{
    this->lasttick = 0;
}

void Limiter::update()
{
    this->lasttick = SDL_GetTicks();
}

void delay(uint32_t utime)
{
    SDL_Delay(utime);
}

// =============================================================================
// Initialisers

void init()
{
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_JOYSTICK) == -1)
        throw std::runtime_error(std::string("SDL_Init(): ") + SDL_GetError());

    if (TTF_Init()==-1)
        throw std::runtime_error(std::string("TTF_Init(): ") + TTF_GetError());

    SDL_EnableUNICODE(true);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
}

void uninit()
{
    TTF_Quit();
    SDL_Quit();
}
// =============================================================================
// Color

Colour::Colour()
{
    this->r = 0;
    this->g = 0;
    this->b = 0;
    this->a = 0;
}

Colour::Colour(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
    this->r = r;
    this->g = g;
    this->b = b;
    this->a = a;
}

bool operator== (Colour &c1, Colour &c2)
{
    if (c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c1.a)
        return true;
    return false;
}

bool operator!= (Colour &c1, Colour &c2)
{
    return !(c1 == c2);
}

// =============================================================================
// Surface

Surface::Surface(const Surface& s)
{
    count = s.count;
    mSurface = s.mSurface;
    ++*count;
}

Surface::Surface()
{
    count = new long(0);
    mSurface = NULL;
}

Surface::~Surface()
{
    destroy();
}

int Surface::w()
{
    if (mSurface == NULL)
        throw std::runtime_error("sdl::Surface::w(): Surface not set!");
    return mSurface->w;
}

int Surface::h()
{
    if (mSurface == NULL)
        throw std::runtime_error("sdl::Surface::h(): Surface not set!");
    return mSurface->h;
}

Rect Surface::size()
{
    if (mSurface == NULL)
        throw std::runtime_error("sdl::Surface::size(): Surface not set!");
    return Rect(0, 0, mSurface->w, mSurface->h);
}

int Surface::depth()
{
    if (mSurface == NULL)
        throw std::runtime_error("sdl::Surface::depth(): Surface not set!");
    return mSurface->format->BitsPerPixel;
}

void Surface::open(SDL_Surface * surf)
{
    destroy();
    if (surf != NULL)
    {
        count = new long(1);
        mSurface = surf;
    }
}

void Surface::destroy()
{
    if (mSurface != NULL)
    {
        if (--*count == 0)
        {
            SDL_FreeSurface(mSurface);
            mSurface = NULL;
            delete count;
        }
    }
}

void Surface::fill(Rect box, Colour colour)
{
    if (mSurface == NULL)
        throw std::runtime_error("sdl::Surface::depth(): Surface not set!");

    SDL_Surface *temp = SDL_CreateRGBSurface( SDL_SWSURFACE | SDL_SRCALPHA, box.w, box.h, 32, Rmask, Gmask, Bmask, Amask);
    if (temp == NULL)
        throw std::runtime_error(std::string("SDL_CreateRGBSurface(): ") + SDL_GetError());

    uint32_t bitcolour = SDL_MapRGBA(temp->format, colour.r, colour.g, colour.b, colour.a);

    if (SDL_FillRect(temp, NULL, bitcolour) == -1)
        throw std::runtime_error(std::string("SDL_FillRect(): ") + SDL_GetError());

    SDL_DisplayFormatAlpha(temp);
    if (temp == NULL)
        throw std::runtime_error(std::string("SDL_DisplayFormatAlpha(): ") + SDL_GetError());

    if (SDL_BlitSurface(temp, NULL, mSurface, &box) == -1)
        throw std::runtime_error(std::string("SDL_BlitSurface(): ") + SDL_GetError());

    SDL_FreeSurface(temp);
}

Surface& Surface::operator= (const Surface& s)
{
    if (this != &s)
    {
        destroy();
        mSurface = s.mSurface;
        count = s.count;
        ++*count;
    }
    return *this;
}

// =============================================================================
// Display

Display::Display(int w, int h, int depth, uint32_t flags) : Surface()
{
    create(w, h, depth, flags);
}

void Display::create(int w, int h, int depth, uint32_t flags)
{
    destroy();
    open(SDL_SetVideoMode(w, h, depth, flags));
    if (mSurface == NULL)
    {
        throw std::runtime_error(std::string("SDL_SetVideoMode(): ") + SDL_GetError());
    }
}

bool Display::fullscreen()
{
    if (mSurface != NULL)
    {
        return ((mSurface->flags & SDL_FULLSCREEN) != 0);
    }
    else
    {
        throw std::runtime_error("sdl::Screen::fullscreen(): Surface not set!");
    }
    return false;
}

bool Display::fullscreen(bool value)
{
    if (mSurface != NULL)
    {
        uint32_t flags = mSurface->flags;
        if (((flags & SDL_FULLSCREEN) != 0) != value)
        {
            flags = flags^SDL_FULLSCREEN;
        }
        create(w(), h(), depth(), flags);
    }
    else
    {
        throw std::runtime_error("sdl::Screen::fullscreen(): Surface not set!");
    }
    return fullscreen();
}

void Display::update()
{
    if (mSurface != NULL)
    {
        if (SDL_Flip(mSurface) == -1)
        {
            std::runtime_error(std::string("SDL_Flip(): ") + SDL_GetError());
        }
    }
    else
    {
        throw std::runtime_error("sdl::Screen::update(): Surface not set!");
    }
}

void Display::title(std::string text)
{
    SDL_WM_SetCaption(text.c_str(), text.c_str());
}

void Display::icon(Surface* image)
{
    SDL_WM_SetIcon(image->mSurface, NULL);
}

// =============================================================================
// Image

Image::Image(std::string filename) : Surface()
{
    open(filename);
}

void Image::open(std::string filename)
{
    destroy();
#ifndef SDLPP_SKIP_IMAGE
    open(IMG_Load(filename.c_str()));
#else
    open(SDL_LoadBMP(filename.c_str()));
#endif
    if (mSurface == NULL)
    {
        throw std::runtime_error(std::string("IMG_Load(): ") + SDL_GetError());
    }
}

void Image::open(SDL_Surface * surf)
{
    Surface::open(surf);
}

void Image::create(int w, int h, uint32_t depth, uint32_t flags)
{
    destroy();
    open(SDL_DisplayFormat(SDL_CreateRGBSurface(flags, w, h, depth, Rmask, Gmask, Bmask, Amask)));
    if (mSurface == NULL)
    {
        throw std::runtime_error(std::string("SDL_CreateRGBSurface(): ") + SDL_GetError());
    }
}

void Image::render(Surface* target)
{
    render(size(), Rect(0, 0), target);
}

void Image::render(Rect dest, Surface* target)
{
    render(size(), dest, target);
}

void Image::render(Rect src, Rect dest, Surface* target)
{
    if (SDL_BlitSurface(mSurface, &src, target->mSurface, &dest) == -1)
    {
        throw std::runtime_error(std::string("SDL_BlitSurface(): ") + SDL_GetError());
    }
}

#ifndef SDLPP_SKIP_TTF
// =============================================================================
// Text

Text::Text(): mImage()
{
    font = NULL;
    colour = Colour();
    aalias = true;
    text = "";
}
Text::Text(Font & fnt, Colour col, bool aa): mImage()
{
    font = &fnt;
    colour = col;
    aalias = aa;
    text = "";
}


Text::~Text(){}

void Text::redraw()
{
    if (oText == text && oColour == oColour && oFont == font && oAalias == aalias)
        return;
    oText = text;
    oColour = colour;
    oFont = font;
    oAalias = aalias;
    if (aalias)
    {
        mImage.open(TTF_RenderText_Blended(font->mFont, text.c_str(), colour));
    }
    else
    {
        mImage.open(TTF_RenderText_Solid(font->mFont, text.c_str(), colour));
    }
    if (mImage.mSurface == NULL)
    {
        if (aalias)
        {
            throw std::runtime_error(std::string("TTF_RenderText_Blended: ") + TTF_GetError());
        }
        else
        {
            throw std::runtime_error(std::string("TTF_RenderText_Solid: ") + TTF_GetError());
        }
    }
}

Rect Text::size()
{
    if (text.length() == 0 && font != NULL)
        return Rect(0, 0, 0, font->h());
    return font->size(text);
}

void Text::render(Rect dest, Surface* target)
{
    render(size(), dest, target);
}

void Text::render(Rect src, Rect dest, Surface* target)
{
    if (text.length() == 0 && font != NULL)
        return;
    redraw();
    mImage.render(src, dest, target);
}

// =============================================================================
// Font

Font::Font()
{
    mFont = NULL;
    count = new long(0);
}

Font::Font(const Font& s)
{
    count = s.count;
    mFont = s.mFont;
    ++*count;
}

Font::Font(std::string filename, int size, int index)
{
    mFont = NULL;
    count = new long(0);
    open(filename, size, index);
}

Font::~Font()
{
    destroy();
}

void Font::open(std::string filename, int size, int index)
{
    destroy();
    mFont = TTF_OpenFontIndex(filename.c_str(), size, index);
    if (mFont == NULL)
    {
        throw std::runtime_error(std::string("TTF_OpenFont: ") + TTF_GetError());
    }
}

int Font::w(std::string &s)
{
    int width;
    TTF_SizeText(mFont, s.c_str(), &width, NULL);
    return width;
}

int Font::h()
{
    return TTF_FontHeight(mFont);
}

Rect Font::size(std::string &s)
{
    int width, height;
    TTF_SizeText(mFont, s.c_str(), &width, &height);
    return Rect(0, 0, width, height);
}

void Font::destroy()
{
    if (mFont != NULL)
    {
        if (--*count == 0)
        {
            TTF_CloseFont(mFont);
            mFont = NULL;
            delete count;
        }
    }
}

Font& Font::operator= (const Font& f)
{
    if (this != &f)
    {
        destroy();
        mFont = f.mFont;
        count = f.count;
        ++*count;
    }
    return *this;
}
#endif

}
